package com.jl.sms.userdata;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.annotations.JSConstructor;
import org.mozilla.javascript.annotations.JSGetter;
import org.mozilla.javascript.annotations.JSSetter;
import org.mozilla.javascript.annotations.JSStaticFunction;

import com.jl.util.HexUtil;

public class UserDataParam extends ScriptableObject {

	private static final long serialVersionUID = 9187521592517384572L;
	
	private String spi = "00 00";
	private String kic = "00";
	private String kid = "00";
	private String keyc = "01 23 45 67 89 AB CD EF";
	private String keyd = "01 23 45 67 89 AB CD EF";
	private String pcntr = "00";
	private String tar = "00 00 00";
	private String cntr = "00 00 00 00 00";
	private String mac = "";
	private String macLen = "08";

	public UserDataParam() {
	};

	@JSConstructor
	public UserDataParam(String spi_kic_kid, String tar, String keyc,
			String keyd) {
		this.spi = HexUtil.subHex(spi_kic_kid, 0, 2);
		this.kic = HexUtil.subHex(spi_kic_kid, 2, 3);
		this.kid = HexUtil.subHex(spi_kic_kid, 3, 4);
		this.tar = tar;
		this.keyc = keyc;
		this.keyd = keyd;
	}

	public int getMacLen() {
		return HexUtil.parseHexToByte(macLen) & 0xFF;
	}

	@JSSetter
	public void setMacLen(String macLen) {
		this.macLen = macLen;
	}

	@JSGetter
	public String getMac() {
		return mac;
	}

	public void setMac(String mac) {
		this.mac = mac;
	}

	@JSGetter
	public String getSpi() {
		return spi;
	}

	@JSSetter
	public void setSpi(String spi) {
		this.spi = spi;
	}

	@JSGetter
	public String getKic() {
		return kic;
	}

	@JSSetter
	public void setKic(String kic) {
		this.kic = kic;
	}

	@JSGetter
	public String getKid() {
		return kid;
	}

	@JSSetter
	public void setKid(String kid) {
		this.kid = kid;
	}

	@JSGetter
	public String getKeyc() {
		return keyc;
	}

	@JSSetter
	public void setKeyc(String keyc) {
		this.keyc = keyc;
	}

	@JSGetter
	public String getKeyd() {
		return keyd;
	}

	@JSSetter
	public void setKeyd(String keyd) {
		this.keyd = keyd;
	}

	@JSGetter
	public String getTar() {
		return tar;
	}

	@JSSetter
	public void setTar(String tar) {
		this.tar = tar;
	}

	@JSGetter
	public String getCntr() {
		return cntr;
	}

	@JSSetter
	public void setCntr(String cntr) {
		this.cntr = cntr;
	}

	@JSGetter
	public String getPcntr() {
		return pcntr;
	}

	public void setPcntr(String pcntr) {
		this.pcntr = pcntr;
	}

	public Cipher getCipher(int mode) throws NoSuchAlgorithmException,
			NoSuchPaddingException, InvalidKeyException,
			InvalidAlgorithmParameterException {
		byte bspi = HexUtil.parseHexToByte(spi.trim().substring(0, 2));
		if ((bspi & 0x04) == 0x04) {
			byte bkic = HexUtil.parseHexToByte(kic.trim());
			switch (bkic & 0x03) {
			case 0x01:
				return getDesCipher(mode, bkic, "kic");
			case 0x00:// Algorithm known implicitly by both entities
			case 0x02:// Reserved
			case 0x03:// proprietary Implementations
				return null;
			}
		} else {
			return null;
		}
		return null;
	}

	public Cipher getCryptChecksum() throws NoSuchAlgorithmException,
			NoSuchPaddingException, InvalidKeyException,
			InvalidAlgorithmParameterException {
		byte bspi = HexUtil.parseHexToByte(spi.trim().substring(0, 2));
		switch (bspi & 0x03) {
		case 0x02:
			byte bkid = HexUtil.parseHexToByte(kid);
			return getDesCipher(Cipher.ENCRYPT_MODE, bkid, "kid");
		case 0x00:
		case 0x01:
		case 0x03:
			return null;
		}
		return null;
	}

	private Cipher getDesCipher(int mode, byte bkic, String type)
			throws NoSuchAlgorithmException, NoSuchPaddingException,
			InvalidKeyException, InvalidAlgorithmParameterException {
		String cipherDescript = "DES";
		String name = "DES";
		boolean isCbc = true;
		switch (bkic & 0x0c) {
		case 0x00:
			cipherDescript += "/CBC";
			break;
		case 0x04:
			cipherDescript += "ede/CBC";
			name += "ede";
			motifyKey16To24(type);
			break;
		case 0x08:
			cipherDescript += "ede/CBC";
			name += "ede";
			motifyKey16To24(type);
			break;
		case 0x0C:
			if (type.equalsIgnoreCase("lid")) {
				return null;
			}
			cipherDescript += "/ECB";
			isCbc = false;
			break;
		}
		cipherDescript += "/NoPadding";
		Cipher cipher = Cipher.getInstance(cipherDescript);

		if (cipher != null) {
			SecretKeySpec ksp = null;
			if (isCbc) {
				IvParameterSpec ivp = new IvParameterSpec(new byte[] { 0, 0, 0,
						0, 0, 0, 0, 0, });
				if (type.equalsIgnoreCase("kic")) {
					ksp = new SecretKeySpec(HexUtil.parseHexToByteArray(keyc),
							name);
					cipher.init(mode, ksp, ivp);
				} else {
					ksp = new SecretKeySpec(HexUtil.parseHexToByteArray(keyd),
							name);
					cipher.init(mode, ksp, ivp);
				}
			} else {
				if (type.equalsIgnoreCase("kic")) {
					ksp = new SecretKeySpec(HexUtil.parseHexToByteArray(keyc),
							name);
					cipher.init(mode, ksp);
				} else {
					ksp = new SecretKeySpec(HexUtil.parseHexToByteArray(keyd),
							name);
					cipher.init(mode, ksp);
				}
			}
		}
		return cipher;
	}

	private void motifyKey16To24(String type) {
		if (type.trim().equalsIgnoreCase("kic")) {
			if (HexUtil.getHexCount(keyc) == 16)
				this.keyc = this.keyc + HexUtil.compact(keyc).substring(0, 16);
		} else {
			if (HexUtil.getHexCount(keyd) == 16)
				this.keyd = this.keyd + HexUtil.compact(keyd).substring(0, 16);
		}
	}

	/**
	 * parse the spi and kic ,return the desctiption of the ctypt algrithm.
	 * 
	 * @param spi
	 * @param kic
	 * @return
	 */
	@JSStaticFunction
	public static String getKicDescribe(String spi, String kic) {
		String res = "";
		if ((HexUtil.parseHexToByte(HexUtil.subHex(spi, 0, 1)) & 0x04) == 0x04) {
			res += "ciphering:";
			res += parseKIByte(kic, false);
		} else {
			res += "no ciphering";
		}
		return res;
	}

	/**
	 * parse the spi and kid ,return the desctiption of the signature algrithm.
	 * 
	 * @param spi
	 * @param kid
	 * @return
	 */
	@JSStaticFunction
	public static String getKidDescribe(String spi, String kid) {
		String res = "";
		switch (HexUtil.parseHexToByte(HexUtil.subHex(spi, 0, 1)) & 0x03) {
		case 0x00:
			res += "No RC, CC or DS";
			break;
		case 0x01:
			res += "RC:";
			break;
		case 0x02:
			res += "CC:";
			res += parseKIByte(kid, true);
			break;
		case 0x03:
			res += "DS:";
			break;
		}
		return res;
	}

	private static String parseKIByte(String ki, boolean iskid) {
		String res = "";
		switch (HexUtil.parseHexToByte(ki) & 0x03) {
		case 0x00:
			res += "both konwn";
			break;
		case 0x01:
			res += getDESDescribe(ki, iskid);
			break;
		case 0x02:
			res += "reserve";
			break;
		case 0x03:
			res += "proprietary";
			break;
		}
		return res;
	}

	private static String getDESDescribe(String k, boolean iskid) {
		String res = "";
		switch (HexUtil.parseHexToByte(k) & 0x0C) {
		case 0x00:
			res += "DES CBC";
			break;
		case 0x04:
			res += "3DES2KEY CBC";
			break;
		case 0x08:
			res += "3DES3KEY CBC";
			break;
		case 0x0C:
			if (iskid) {
				res += "Reserved";
			} else {
				res += "DES ECB";
			}
			break;
		}
		return res;
	}

	@Override
	public String getClassName() {
		return "UserDataParam";
	}
}
